import argparse
import time, socket
import NLAgent
import threading
import queue as Queue
import socket


parser = argparse.ArgumentParser(parents=[NLAgent.parser])
client_args = parser.add_argument_group('Client Behavior')
client_args.add_argument('--max_conn', action="store", dest='max_clients', default=10)

class NLAgentTCPClient():
  def __init__(self,db_filename, master_start_time, min_workers=5, max_workers=100, min_spare_workers=5):
    self.start_time = time.time()
    self.db_filename = db_filename
    self.master_start_time = master_start_time
    self.request_queue = Queue.Queue()
    self.min_workers = min_workers
    self.max_workers = max_workers
    self.min_spare_workers = min_spare_workers
    self.num_workers = 0
    self.num_busy_workers = 0
    self.workers_mutex = threading.Lock()
    self.start_workers(self.min_workers)
  
  def start_workers(self, n):
    for i in range(n):
      t = threading.Thread(target = self.worker)
      t.setDaemon(True)
      t.start()
  
  def do_connection(self, db, conn_start, conn_id):
    #print("Thread ", threading.current_thread().name, " doing connection", conn_id);
    wait_for = conn_start - (time.time() - self.start_time - float(self.master_start_time))
    if wait_for <= 0:
      time.time()
      # Crap, we've passed when we should start this!
     # print("We're running behind. We should have started this ", -1 * wait_for," seconds ago.");
    else:
      #print("Waing to start in",wait_for,"seconds .")
      time.sleep(wait_for)
    #print("I'm thread ", threading.current_thread().name, "Doing connection start@ ",conn_start)
    connection = db.get_connection(conn_id)
    my_ip = connection['src']
    my_port = connection['src_port']
    print("I'm thread ", threading.current_thread().name, "Acting as ", connection['src'], " conn id " , conn_id, " doing connection start@ ",conn_start)   
    
    socket_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    try:
      s = socket.socket(socket_family, socket_type)
      s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
      #s.settimeout(3)
    except socket.error:
      print("Failed to do conn", conn_id, ":",msg)
      return
    try: 	 
      s.bind((my_ip,my_port))
    except socket.error:
      print ("Failed to bind to ",my_ip,":",my_port)
      s.close()
      return
    
    #print("Successfully bound to address:", s.getsockname())

    try:
      s.connect((connection['dst'],connection['dst_port']))
    except socket.error:
      s.close()
      print("Could not connect to server:",connection['dst'],":",connection['dst_port'], "from ", my_ip)
      return
    
    #NLAgent.handle_communication(db,s,self.master_start_time,1024,conn_id=conn_id)
    print("Connection ", conn_id, " successful")
    s.close()
        
  def worker(self):
    with self.workers_mutex:
      self.num_workers += 1
    db = NLAgent.db_connect(self.db_filename)
    while True:
      # Connection start
      (conn_start, conn_id) = self.request_queue.get()
      with self.workers_mutex:
        self.num_busy_workers += 1
      
      #print("Got start:", conn_start, "and id", conn_id)
      self.do_connection(db, conn_start, conn_id)
      self.request_queue.task_done()
      
      with self.workers_mutex:
        self.num_busy_workers -= 1
        if self.num_workers-self.num_busy_workers > self.min_spare_workers:
          self.num_workers -= 1
          db.close()
          return
  
  
  def process_request(self, request):
    self.request_queue.put(request)
    with self.workers_mutex:
      if self.request_queue.qsize() > 3 and self.num_workers < self.max_workers:
        self.start_workers(1)

  def join(self):
    self.request_queue.join()      
      

def run_clients(db_filename, master_start_time, my_ips):
  db = NLAgent.db_connect(db_filename)
  
  client_master = NLAgentTCPClient(db_filename, master_start_time, 10, 100, 5)
  
  db_start = -1;
  for connection in db.get_list_of_connections():
    if db_start == -1:
      db_start = connection['start_time']
    if connection['src'] in my_ips:
      st = float(master_start_time)+float(connection['start_time']-float(db_start))
      #print("Scheduling connection id ",connection['conn_id'], "at ", st) 
      request = (st, connection['conn_id'])
      client_master.process_request(request)
  
  client_master.join() 
    
if __name__ == '__main__':
  args = parser.parse_args()
  run_clients(args.filename, args.master_start_time, args.client_list)

  
